from typing import List, Set, Callable, Tuple
from abc import ABC, abstractmethod

from chernc.treediff.gumtree.matchers.configuration_options import ConfigurationOptions
from chernc.treediff.gumtree.matchers.gumtree_properties import GumtreeProperties
from chernc.treediff.gumtree.matchers.heuristic.gt.default_priority_tree_queue import DefaultPriorityTreeQueue
from chernc.treediff.gumtree.matchers.heuristic.gt.hash_based_mapper import HashBasedMapper
from chernc.treediff.gumtree.matchers.heuristic.gt.priority_tree_queue import PriorityTreeQueue
from chernc.treediff.gumtree.matchers.mapping_store import MappingStore
from chernc.treediff.gumtree.matchers.matcher import Matcher
from chernc.treediff.gumtree.tree.tree import TreeLike


class AbstractSubtreeMatcher(Matcher):
    DEFAULT_MIN_PRIORITY = 1
    DEFAULT_PRIORITY_CALCULATOR = "height"

    def __init__(self):
        self.min_priority = self.DEFAULT_MIN_PRIORITY
        self.priority_calculator = PriorityTreeQueue.get_priority_calculator(self.DEFAULT_PRIORITY_CALCULATOR)
        self.src = None
        self.dst = None
        self.mappings = None

    def configure(self, properties: "GumtreeProperties") -> None:
        self.min_priority: int = properties.try_configure(ConfigurationOptions.st_minprio, self.min_priority)
        self.priority_calculator: Callable[[TreeLike], int] = PriorityTreeQueue.get_priority_calculator(
            properties.try_configure(ConfigurationOptions.st_priocalc, self.DEFAULT_PRIORITY_CALCULATOR)
        )
        

    def match(self, src: "TreeLike", dst: "TreeLike", mappings: "MappingStore") -> "MappingStore":
        self.src = src
        self.dst = dst
        self.mappings = mappings

        ambiguousMappings = []

        srcTrees = DefaultPriorityTreeQueue(src, self.min_priority, self.priority_calculator)
        dstTrees = DefaultPriorityTreeQueue(dst, self.min_priority, self.priority_calculator)

        while PriorityTreeQueue.synchronize(srcTrees, dstTrees):
            localHashMappings = HashBasedMapper()
            localHashMappings.add_srcs(srcTrees.pop())
            localHashMappings.add_dsts(dstTrees.pop())

            for pair in localHashMappings.unique():
                mappings.add_mapping_recursively(next(iter(pair[0])), next(iter(pair[1])))

            for pair in localHashMappings.ambiguous():
                ambiguousMappings.append(pair)

            for pair in localHashMappings.unmapped():
                for tree in pair[0]:
                    srcTrees.open(tree)
                for tree in pair[1]:
                    dstTrees.open(tree)

        self.handle_ambiguous_mappings(ambiguousMappings)
        return self.mappings

    @abstractmethod
    def handle_ambiguous_mappings(self, ambiguous_mappings: List[Tuple[Set[TreeLike], Set[TreeLike]]]) -> None:
        pass

    def get_min_priority(self) -> int:
        return self.min_priority

    def set_min_priority(self, min_priority: int) -> None:
        self.min_priority = min_priority

    def get_applicable_options(self) -> Set["ConfigurationOptions"]:
        return {ConfigurationOptions.st_priocalc, ConfigurationOptions.st_minprio}
